//开发者可以使用这个类来创建UDP客户端或服务器，进行网络通信。
//例如，一个客户端可以使用Socket()创建套接字，Bind()绑定到一个端口，然后使用SendTo()发送数据到服务器。
//服务器则可以使用Socket()创建套接字，Bind()绑定到一个端口，然后循环调用RecvFrom()来接收来自客户端的数据。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<cassert>
#include<string>

#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;

//udp套接字
class UdpSocket
{
 public:
 //初始化UdpSocket对象，并设置文件描述符fd_为-1，表示当前没有打开的套接字。
 UdpSocket()
 :fd_(-1)
 {}

//建立udp协议
 bool Socket()
 {
     //socket(tcp/udp套接字地址类型（af_inet代表ipv4 32位），套接字类型（sock_dgram代表使用udp），套接字的协议号 一班默认为0)；
    fd_ =socket(AF_INET,SOCK_DGRAM,0);
    
    if(fd_<0)
    {
        perror("socket");
        return false;
    }
    return true;
 }

//关闭
 bool Close()
 {
    close(fd_);
    return true;
 }
//使UDP套接字能够监听和接收来自指定IP地址和端口的数据
 bool Bind(const std::string&ip,uint16_t port)  //传入ip地址和端口号
 {
    sockaddr_in addr;  //用于存储套接字的地址信息
    addr.sin_family =AF_INET;//设置套接字的地址族为AF_INET，即IPv4地址。

    // 将传入的IP地址字符串转换为网络字节序的二进制形式，并存储在addr的sin_addr成员中。
    // inet_addr函数接受一个指向IP地址字符串的指针，并返回一个代表该IP地址的网络字节序的32位整数
    addr.sin_addr.s_addr =inet_addr(ip.c_str());
    
    // 设置套接字的端口号。htons函数将主机字节序的端口号转换为网络字节序。
    addr.sin_port =htons(port);

    // 调用bind函数，实际进行套接字的绑定操作。
    // _fd是类的私有成员变量，代表UDP套接字的文件描述符。
    // (sockaddr*)&addr是将sockaddr_in结构体转换为sockaddr结构体指针的类型转换。
    int ret =bind(fd_,(sockaddr*)&addr,sizeof(addr));
    if(ret<0)
    {
        perror("bind");
        return false;
    }
    return true;
 }

// 定义RecvFrom函数，用于从UDP套接字接收数据。
// 函数接收三个可选参数：buf用于存储接收到的数据，ip用于存储发送方的IP地址，port用于存储发送方的端口号。
bool RecvFrom(std::string* buf, std::string* ip = NULL, uint16_t* port = NULL) 
{
   char tmp[1024 * 10] = {0}; //接收数据的缓冲区

   sockaddr_in peer; //存储发送方地址信息
   socklen_t len = sizeof(peer); //表示 peer结构体大小
   
    // 调用recvfrom函数，从套接字fd_接收数据。
    // tmp是接收缓冲区，sizeof(tmp) - 1是缓冲区大小减1，确保有空间存放字符串的结束符'\0'。
    // 0是接收数据的 flags，通常设置为0。
    // (sockaddr*)&peer是指向发送方地址信息的结构体指针。
    // &len是指向发送方地址结构体大小的指针。
    // recvfrom函数返回接收到的字节数，如果接收失败则返回小于0的值。
   ssize_t read_size = recvfrom(fd_, tmp, sizeof(tmp) - 1, 0, (sockaddr*)&peer, &len);
   if (read_size < 0) 
   {
   perror("recvfrom");
   return false;
   }

    // 将读到的缓冲区内容放到输出参数中

   // 使用std::string的assign方法，将tmp数组中的数据复制到buf指向的字符串中。
   buf->assign(tmp, read_size);
   // 如果ip参数非NULL，将发送方的IP地址转换为可读的点分十进制格式，并存储到ip指向的字符串中。
   if (ip != NULL) 
   {
   *ip = inet_ntoa(peer.sin_addr);
   }
   // 如果port参数非NULL，将发送方的端口号从网络字节序转换为主机字节序，并存储到port指向的变量中。
   if (port != NULL) 
   {
   *port = ntohs(peer.sin_port);
   }
    return true;
 }

// 定义SendTo函数，用于通过UDP套接字发送数据。
// 函数接收三个参数：buf是要发送的数据，ip是目标IP地址，port是目标端口号。
 bool SendTo(const std::string& buf, const std::string& ip, uint16_t port)
{
   sockaddr_in addr;//存储目标套接字的地址信息。
   addr.sin_family = AF_INET;
   addr.sin_addr.s_addr = inet_addr(ip.c_str());
   addr.sin_port = htons(port);

    // 调用sendto函数，将数据从套接字fd_发送到指定的目标地址。
    // buf.data()是std::string的成员函数，返回一个指向字符串数据的指针。
    // buf.size()是std::string的成员函数，返回字符串的长度。
    // 0是发送数据的 flags，通常设置为0。
    // (sockaddr*)&addr是指向目标地址结构体的指针。
    // sizeof(addr)是目标地址结构体的大小。
    // sendto函数返回发送的字节数，如果发送失败则返回小于0的值。
   ssize_t write_size = sendto(fd_, buf.data(), buf.size(), 0, (sockaddr*)&addr, sizeof(addr));
   if (write_size < 0) 
   {  
     perror("sendto");
     return false;
   }

   return true;
 }

 private:
 int fd_;
};
